package multiThread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 三个不同的线程 A、B、C 将会共用一个 Foo 实例。
 * <p>
 * 一个将会调用 first() 方法
 * 一个将会调用 second() 方法
 * 还有一个将会调用 third() 方法
 * 请设计修改程序，以确保 second() 方法在 first() 方法之后被执行，third() 方法在 second() 方法之后被执行。
 * </p>
 *
 * @author fangXK
 * @date 2021/4/16 11:58
 */
public class S_1114 {
    /**
     * 方法一：Lock锁
     */
    static class Foo1 {
        int num;
        Lock lock;
        Condition condition1, condition2, condition3;

        public Foo1() {
            num = 1;
            lock = new ReentrantLock();
            condition1 = lock.newCondition();
            condition2 = lock.newCondition();
            condition3 = lock.newCondition();
        }

        // 第一个线程要执行的方法
        public void first(Runnable printFirst) throws InterruptedException {
            lock.lock();
            try {
                while (num != 1) {
                    condition1.await();
                }
                // printFirst.run() outputs "first". Do not change or remove this line.
                printFirst.run();
                num = 2;
                condition2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void second(Runnable printSecond) throws InterruptedException {

            lock.lock();
            try {
                while (num != 2) {
                    condition2.await();
                }
                // printSecond.run() outputs "second". Do not change or remove this line.
                printSecond.run();
                num = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void third(Runnable printThird) throws InterruptedException {
            lock.lock();
            try {
                while (num != 3) {
                    condition3.await();
                }
                // printThird.run() outputs "third". Do not change or remove this line.
                printThird.run();
                num = 1;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    /**
     * 方法二：Semaphore信号量
     */
    static class Foo2 {

        Semaphore semaphore12, semaphore23;

        public Foo2() {
            // 初始的允许请求均设为0,表示没有这个信号量
            semaphore12 = new Semaphore(0);
            semaphore23 = new Semaphore(0);
        }

        public void first(Runnable printFirst) throws InterruptedException {

            // printFirst.run() outputs "first". Do not change or remove this line.
            printFirst.run();

            // 释放一个12的信号量
            semaphore12.release();
        }

        public void second(Runnable printSecond) throws InterruptedException {
            // 获取一个12的信号量，没有则阻塞
            semaphore12.acquire();

            // printSecond.run() outputs "second". Do not change or remove this line.
            printSecond.run();

            // 释放一个23的信号量
            semaphore23.release();
        }

        public void third(Runnable printThird) throws InterruptedException {
            // 获取一个23的信号量，因为初始化为0，所以一开始执行会获取不到，该线程就会阻塞
            semaphore23.acquire();
            // printThird.run() outputs "third". Do not change or remove this line.
            printThird.run();
        }
    }

    /**
     * 方法三：CountDownLatch计数器,后者顺序的线程都需要阻塞等待
     */
    static class Foo3 {

        CountDownLatch countDownLatch12, countDownLatch23;

        public Foo3() {
            countDownLatch12 = new CountDownLatch(3);
            countDownLatch23 = new CountDownLatch(3);
        }

        public void first(Runnable printFirst) throws InterruptedException {
            countDownLatch12.countDown();
            // printFirst.run() outputs "first". Do not change or remove this line.
            printFirst.run();
        }

        public void second(Runnable printSecond) throws InterruptedException {
            // 等待计数器为0时再执行
            countDownLatch12.await();
            // printSecond.run() outputs "second". Do not change or remove this line.
            printSecond.run();
            countDownLatch23.countDown();
        }

        public void third(Runnable printThird) throws InterruptedException {
            // 等待线程2去释放
            countDownLatch23.await();
            // printThird.run() outputs "third". Do not change or remove this line.
            printThird.run();
        }
    }

    class Foo4 {

        public Foo4() {

        }

        public void first(Runnable printFirst) throws InterruptedException {

            // printFirst.run() outputs "first". Do not change or remove this line.
            printFirst.run();
        }

        public void second(Runnable printSecond) throws InterruptedException {

            // printSecond.run() outputs "second". Do not change or remove this line.
            printSecond.run();
        }

        public void third(Runnable printThird) throws InterruptedException {

            // printThird.run() outputs "third". Do not change or remove this line.
            printThird.run();
        }
    }

    public static void main(String[] args) {
        Foo3 foo1 = new Foo3();
        Thread thread1 = new Thread(() -> {
            try {
                foo1.first(() -> {
                    System.out.println("one\n");
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread thread2 = new Thread(() -> {
            try {
                foo1.second(() -> {
                    System.out.println("two\n");
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread thread3 = new Thread(() -> {
            try {
                foo1.third(() -> {
                    System.out.println("three\n");
                });
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread3.start();
        thread2.start();
        thread1.start();
    }
}
